// SPDX-License-Identifier: MIT
/*
 * Copyright (c) 2025 HiSilicon Technologies Co., Ltd. All rights reserved.
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 */

#include <stdint.h>
#include <sys/ioctl.h>
#include "cdma_u_cmd.h"
#include "cdma_u_queue.h"

static int cdma_u_cmd_create_queue(struct dma_context *ctx,
				   struct cdma_u_queue *cdma_queue,
				   struct queue_cfg *cfg)
{
	struct cdma_cmd_create_queue_args arg = { 0 };
	struct cdma_ioctl_hdr hdr;
	int ret;

	if (!ctx->dma_dev || ctx->dma_dev->fd < 0) {
		CDMA_LOG_ERR("create queue cmd parameter invalid.\n");
		return -EINVAL;
	}

	hdr.command = (uint32_t)CDMA_CMD_CREATE_QUEUE;
	hdr.args_len = (uint32_t)sizeof(arg);
	hdr.args_addr = (uint64_t)&arg;
	arg.in.queue_depth = cfg->queue_depth;
	arg.in.dcna = cfg->dcna;
	arg.in.priority = cfg->priority;
	arg.in.rmt_eid = cfg->rmt_eid.dw0;
	arg.in.user_ctx = cfg->user_ctx;
	arg.in.trans_mode = cfg->trans_mode;

	ret = ioctl(ctx->dma_dev->fd, CDMA_SYNC, &hdr);
	if (ret) {
		CDMA_LOG_ERR("ioctl failed, ret = %d, cmd = %u.\n", ret, hdr.command);
		return ret;
	}

	cdma_queue->handle = arg.out.handle;
	cdma_queue->base.queue_id = arg.out.queue_id;

	return ret;
}

static int cdma_u_cmd_delete_queue(struct cdma_u_queue *cdma_queue)
{
	struct cdma_cmd_delete_queue_args arg = { 0 };
	struct dma_queue *queue = &cdma_queue->base;
	struct cdma_ioctl_hdr hdr;
	int ret;

	if (!queue->ctx || !queue->ctx->dma_dev || queue->ctx->dma_dev->fd < 0) {
		CDMA_LOG_ERR("delete jfs cmd parameter invalid.\n");
		return -EINVAL;
	}

	hdr.command = (uint32_t)CDMA_CMD_DELETE_QUEUE;
	hdr.args_len = (uint32_t)sizeof(arg);
	hdr.args_addr = (uint64_t)&arg;
	arg.in.queue_id = queue->queue_id;
	arg.in.handle = cdma_queue->handle;

	ret = ioctl(queue->ctx->dma_dev->fd, CDMA_SYNC, &hdr);
	if (ret)
		CDMA_LOG_ERR("ioctl failed, ret = %d, cmd = %u.\n", ret, hdr.command);

	return ret;
}

static void cdma_assemble_jfs_cfg(struct dma_jfs_cfg *jfs_cfg,
				  const struct dma_device *dev,
				  const struct queue_cfg *cfg,
				  const uint32_t queue_id)
{
	jfs_cfg->err_timeout = CDMA_TYPICAL_ERR_TIMEOUT;
	jfs_cfg->max_rsge = dev->attr.dev_cap.max_jfs_rsge;
	jfs_cfg->max_sge = dev->attr.dev_cap.max_jfs_sge;
	jfs_cfg->rnr_retry = CDMA_TYPICAL_RNR_RETRY;
	jfs_cfg->eid_idx = dev->attr.eu.eid_idx;
	jfs_cfg->depth = cfg->queue_depth;
	jfs_cfg->priority = cfg->priority;
	jfs_cfg->queue_id = queue_id;
	jfs_cfg->rmt_eid = cfg->rmt_eid.dw0;
	jfs_cfg->trans_mode = cfg->trans_mode;
}

static void cdma_assemble_tp_cfg(struct dma_tp_cfg *tp_cfg,
				 const struct dma_device *dev,
				 const struct queue_cfg *cfg,
				 const uint32_t queue_id)
{
	tp_cfg->seid = dev->attr.eu.eid.dw0;
	tp_cfg->deid = cfg->rmt_eid.dw0;
	tp_cfg->dcna = cfg->dcna;
	tp_cfg->queue_id = queue_id;
}

static void cdma_assemble_jfc_cfg(struct dma_jfc_cfg *jfc_cfg,
				  const struct queue_cfg *cfg,
				  const uint32_t queue_id)
{
	jfc_cfg->depth = cfg->queue_depth;
	jfc_cfg->user_ctx = cfg->user_ctx;
	jfc_cfg->queue_id = queue_id;
}

static int cdma_u_alloc_jfc_res(struct dma_context *ctx,
				struct cdma_u_queue *cdma_queue,
				struct queue_cfg *cfg,
				struct dma_jfc_cfg *jfc_cfg)
{
	struct dma_jfc *jfc;
	int ret = -ENOMEM;

	jfc_cfg->jfce = cdma_u_create_jfce(ctx);
	if (!jfc_cfg->jfce) {
		CDMA_LOG_ERR("cdma jfce create failed.\n");
		return ret;
	}

	jfc = cdma_u_create_jfc(ctx, jfc_cfg);
	if (!jfc) {
		CDMA_LOG_ERR("cdma jfc create failed.\n");
		goto delete_jfce;
	}

	if (cfg->event_mode && cdma_u_rearm_jfc(jfc, false) != DMA_STATUS_OK) {
		CDMA_LOG_ERR("cdma rearm jfc failed.\n");
		ret = -EFAULT;
		goto delete_jfc;
	}

	cdma_queue->cdma_jfce = jfc_cfg->jfce;
	cdma_queue->cdma_jfc = jfc;

	return 0;

delete_jfc:
	(void)cdma_u_delete_jfc(jfc);
delete_jfce:
	(void)cdma_u_delete_jfce(jfc_cfg->jfce);

	return ret;
}

static void cdma_u_free_jfc_res(struct cdma_u_queue *cdma_queue)
{
	(void)cdma_u_delete_jfc(cdma_queue->cdma_jfc);
	(void)cdma_u_delete_jfce(cdma_queue->cdma_jfce);
}

static int cdma_u_create_queue(struct dma_context *ctx, struct queue_cfg *cfg,
			       const uint32_t queue_id,
			       struct cdma_u_queue *cdma_queue)
{
	struct dma_jfc_cfg jfc_cfg = {0};
	struct dma_jfs_cfg jfs_cfg = {0};
	struct dma_tp_cfg tp_cfg = {0};
	int ret;

	if (!ctx || !cfg || !ctx->dma_dev) {
		CDMA_LOG_ERR("create queue parameter invalid.\n");
		return -EINVAL;
	}

	cdma_queue->base.ctx = ctx;
	cdma_queue->base.cfg = *cfg;

	cdma_assemble_jfs_cfg(&jfs_cfg, ctx->dma_dev, cfg, queue_id);
	cdma_assemble_tp_cfg(&tp_cfg, ctx->dma_dev, cfg, queue_id);
	cdma_assemble_jfc_cfg(&jfc_cfg, cfg, queue_id);

	ret = cdma_u_alloc_jfc_res(ctx, cdma_queue, cfg, &jfc_cfg);
	if (ret) {
		CDMA_LOG_ERR("cdma alloc jfc res, ret = %d.\n", ret);
		return ret;
	}

	cdma_queue->cdma_tp = cdma_u_create_ctp(ctx, &tp_cfg);
	if (!cdma_queue->cdma_tp) {
		CDMA_LOG_ERR("cdma tp create failed.\n");
		goto delete_jfc_res;
	}

	jfs_cfg.jfc = cdma_queue->cdma_jfc;
	jfs_cfg.tpn = cdma_queue->cdma_tp->tpn;

	cdma_queue->cdma_jfs = cdma_u_create_jfs(ctx, &jfs_cfg);
	if (!cdma_queue->cdma_jfs) {
		CDMA_LOG_ERR("cdma jfs create failed.\n");
		goto delete_tp;
	}

	cdma_queue->queue_cfg.cdma_jfc_cfg = jfc_cfg;
	cdma_queue->queue_cfg.cdma_jfs_cfg = jfs_cfg;
	cdma_queue->queue_cfg.cdma_tp_cfg = tp_cfg;

	return 0;

delete_tp:
	(void)cdma_u_delete_ctp(cdma_queue->cdma_tp);
delete_jfc_res:
	cdma_u_free_jfc_res(cdma_queue);

	return -ENOMEM;
}

static void cdma_u_delete_queue(struct cdma_u_queue *cdma_queue)
{
	(void)cdma_u_delete_jfs(cdma_queue->cdma_jfs);
	(void)cdma_u_delete_ctp(cdma_queue->cdma_tp);
	cdma_u_free_jfc_res(cdma_queue);
}

struct dma_queue *cdma_alloc_queue(struct dma_context *ctx,
				   struct queue_cfg *cfg)
{
	struct cdma_u_queue *cdma_queue;
	int ret;

	cdma_queue = (struct cdma_u_queue *)calloc(1, sizeof(*cdma_queue));
	if (!cdma_queue) {
		CDMA_LOG_ERR("cdma queue alloc failed.\n");
		return NULL;
	}

	ret = cdma_u_cmd_create_queue(ctx, cdma_queue, cfg);
	if (ret) {
		CDMA_LOG_ERR("cdma queue create failed.\n");
		free(cdma_queue);
		return NULL;
	}

	ret = cdma_u_create_queue(ctx, cfg, cdma_queue->handle, cdma_queue);
	if (ret) {
		CDMA_LOG_ERR("cdma create queue failed.\n");
		(void)cdma_u_cmd_delete_queue(cdma_queue);
		free(cdma_queue);
		return NULL;
	}

	cdma_queue->base.cfg = *cfg;
	cdma_queue->base.ctx = ctx;

	return &cdma_queue->base;
}

void cdma_free_queue(struct dma_queue *queue)
{
	struct cdma_u_queue *cdma_queue;

	cdma_queue = to_cdma_u_queue(queue);

	cdma_u_delete_queue(cdma_queue);
	(void)cdma_u_cmd_delete_queue(cdma_queue);
	free(cdma_queue);
}
